package math.mathExtended;

import function.unary.UnaryFunction;
import java.util.Arrays;
import list.functions.ListFunctions;
import variable.lazy.Lazy;
import variable.lazy.LazyValue;

/**
 * Contains useful methods not available in java.lang.Math. All methods
 * should be statically accessed.
 * 
 * @author Shimu Wu
 */
public class MathExtended {

    public static final double PI = 3.141592653589793238462643383;

    /**
     * Private constructor to discourage instantiation. All methods
     * are static.
     */
    private MathExtended() {
    }

    // <editor-fold defaultstate="collapsed" desc="Basic math functions">
    /**
     * Returns the number square
     *
     * @param a
     * @return
     */
    public static double square(double a) {
        return (a * a);
    }

    /**
     * Returns the logarithm of x with respect to the given base.
     * Returns null if a base less than or equal to 1 is given.
     * 
     * @param base
     * @param value
     * @return the logarithm of x with respect to the given base.
     */
    public static double log(double base, double value) {
        if (base <= 1) {
            return (Double) null;
        }
        return Math.log(value) / Math.log(base);
    }

    /**
     * Returns the factorial of the given integer n. This implementation is
     * NOT efficient (i.e. multiplies all the numbers from 1 to n).
     * 
     * @param n
     * @return n!
     */
    public static int factorial(int n) {
        int product = 1;
        for (int i = 1; i <= n; i++) {
            product *= i;
        }
        return product;
    }
    
    private static final double GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;

    private static final double FIB_CONST = (1 - Math.sqrt(5)) / 2;

    /**
     * Returns the nth fibonacci number (where f0 = 0, f1 = 1). Also works
     * if n is negative.
     * Uses the formula: fn = floor((phi^n - psi^n)/sqrt(5) + 0.5), where: <br />
     * phi = golden ratio = (1 + sqrt(5))/2 = 1.61803 ... <br />
     * psi = (1 - sqrt(5))/2 = -0.61803 ... </ br>
     * Note due to floating point errors, very large n may result in accurate 
     * fibonacci number.
     * 
     * @param index
     * @return 
     */
    public static int fibonacci(int n) {
        return (int) Math.floor(((Math.pow(GOLDEN_RATIO, n)
                - Math.pow(FIB_CONST, n)) / Math.sqrt(5)) + 0.5);
    }

    /**
     * Returns the greatest common divisor (gcd) between the given
     * num1 and num2 using Euclid's algorithm. Returns 0 if both num1 and num2 are 0.
     * 
     * @param num1
     * @param num2
     * @return 
     */
    public static int greatestCommonDivisor(int num1, int num2) {
        final int larger, smaller;
        if (num1 > num2) {
            larger = num1;
            smaller = num2;
        } else {
            larger = num2;
            smaller = num1;
        }
        int dividend, divisor, remainder;
        dividend = larger;
        divisor = smaller;
        remainder = divisor;
        while (remainder != 0) {
            remainder = dividend % divisor;
            dividend = divisor;
            divisor = remainder;
        }
        return dividend;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Vararg methods">
    /**
     * Returns the sum of the given numbers.
     * 
     * @param args
     * @return 
     */
    public static double sum(double... args) {
        double sum = 0;
        for (double element : args) {
            sum += element;
        }
        return sum;
    }

    /**
     * Returns the product of the given numbers.
     * 
     * @param args
     * @return 
     */
    public static double product(double... args) {
        double product = 1;
        for (double element : args) {
            product *= element;
        }
        return product;
    }

    /**
     * Given an arbitrary number of doubles, returns the sum of each argument
     * squared. 
     * 
     * @param args
     * @return 
     */
    public static double sumOfSquares(double... args) {
        return sum(square(args));
    }

    /**
     * Given an arbitrary number of doubles, returns the square root of the
     * sum of each argument squared. One example of usage would be the 
     * Pythagorean Theorem or Euclidean Distance.
     * 
     * @param args
     * @return 
     */
    public static double distance(double... args) {
        return Math.sqrt(sumOfSquares(args));
    }

    /**
     * Returns a double array containing each of the given element squared.
     * 
     * @param args
     * @return 
     */
    public static double[] square(double... args) {
        double[] newArray = new double[args.length];
        for (int i = 0; i < args.length; i++) {
            newArray[i] = args[i] * args[i];
        }
        return newArray;
    }

    /**
     * Returns the average of the given args. Returns 0 if no arguments were
     * given. 
     * @param args
     * @return 
     */
    public static double average(double... args) {
        if (args.length == 0) {
            return 0;
        }
        return sum(args) / args.length;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Degree, Radian conversion">
    /**
     * Converts the given angle from radian to degrees.
     *
     * @param rad the radian to be converted
     * @return the angle in degrees
     */
    public static double radToDegrees(double rad) {
        return (rad * 180 / PI);
    }

    /**
     * Returns a double in radians.
     * 
     * @param degrees the angle in degrees
     * @return the angle in radians
     */
    public static double degreesToRad(double degrees) {
        return (degrees * PI / 180);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Trignometric Functions taking angle in degrees">
    /**
     * Returns the sine of the given angle
     * 
     * @param degrees the angle in degrees
     * @return the sine of the given angle.
     */
    public static double sind(double degrees) {
        return (Math.sin(degreesToRad(degrees)));
    }

    /**
     * Returns the cosine of the given angle.
     * 
     * @param degrees the angle in degrees
     * @return the cosine of the given angle.
     */
    public static double cosd(double degrees) {
        // Converts the parameter given in degrees to radians
        // uses the built in Math class in Java to find the 
        // cosine of the radian angle
        return (Math.cos(degreesToRad(degrees)));

    }

    /**
     * Returns the tangent of the given angle
     * @param degrees
     * @return the degress of the angle
     */
    public static double tand(double degrees) {
        // Converts the parameter given in degrees to radians
        // uses the built in Math class in Java to find the cosine of the radian angle
        return (Math.tan(degreesToRad(degrees)));

    }

    /**
     * Returns the arc sine ([-180, 180] degrees) of a value ([-1, 1]).
     *
     * @param arg
     * @return the arc sine
     */
    public static double aSind(double arg) {
        return radToDegrees(Math.asin(arg));
    }

    /**
     * Returns the arc cosine ([-180, 180] degrees) of a value ([-1, 1]).
     *
     * @param arg
     * @return the arc cosine
     */
    public static double aCosd(double arg) {
        return radToDegrees(Math.acos(arg));
    }

    /**
     * Returns the arc tangent ([-180, 180] degrees) of a value ([-1, 1]).
     *
     * @param arg
     * @return the arc tangent
     */
    public static double aTand(double arg) {
        return radToDegrees(Math.atan(arg));
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Mapping functions">
    /**
     * Maps an integer value from a domain to a range.
     * 
     * @param value the value in the domain
     * @param domainMin the minimum value of the domain
     * @param domainMax the maximum value of the domain
     * @param rangeMin the minimum value of the range
     * @param rangeMax the maximum value of the range
     * @return the value in the range
     */
    public static int map(int value, int domainMin, int domainMax,
            int rangeMin, int rangeMax) {
        return rangeMin
                + (value - domainMin) * (rangeMax - rangeMin)
                / (domainMax - domainMin);
    }

    /**
     * Maps a double value from a domain to a range.
     * 
     * @param value the value in the domain
     * @param domainMin the minimum value of the domain
     * @param domainMax the maximum value of the domain
     * @param rangeMin the minimum value of the range
     * @param rangeMax the maximum value of the range
     * @return the value in the range
     */
    public static double map(double value, double domainMin, double domainMax,
            double rangeMin, double rangeMax) {
        return rangeMin
                + (value - domainMin) * (rangeMax - rangeMin)
                / (domainMax - domainMin);
    }

    /**
     * Clamp the given value (i.e. returns min if the value is less than min,
     * returns max if the value is greater than max, otherwise returns value).
     * @param value
     * @param min
     * @param max
     * @return 
     */
    public static int clamp(int value, int min, int max) {
        if (value < min) {
            return min;
        } else if (value > max) {
            return max;
        }
        return value;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Boolean methods">
    // <editor-fold defaultstate="collapsed" desc="Even or odd">
    /**
     * Returns true if the given number is even.
     * @param num
     * @return 
     */
    public static boolean isEven(int num) {
        return (num % 2) == 0;
    }

    /**
     * Returns true if the given number is odd.
     * @param num
     * @return 
     */
    public static boolean isOdd(int num) {
        return (num % 2) != 0;
    }
    // </editor-fold>

    /**
     * Returns true if the given value is between the given min and max
     * value (inclusive). In other words: true iff value ∈ [min, max]
     * 
     * @param value
     * @param min the minimum value
     * @param max the maximum value
     * @return 
     */
    public static boolean bounded(int value, int min, int max) {
        return (value >= min) && (value <= max);
    }

    /**
     * Returns true if the given value is between the given min and max
     * value (inclusive). In other words: true iff value ∈ [min, max]
     * 
     * @param value
     * @param min the minimum value
     * @param max the maximum value
     * @return 
     */
    public static boolean bounded(long value, long min, long max) {
        return (value >= min) && (value <= max);
    }
    
    // <editor-fold defaultstate="collapsed" desc="Floating point equality">
    /**
     * Returns true if |arg1 - arg2| &lt; EPSILEON, in other words, returns
     * true if arg1 is equal to arg2 within the allowed EPSILEON margin of 
     * error. Should be used instead of the regular == comparison, due to 
     * possibility of floating point round offs.
     *
     * @param arg1 the first value
     * @param arg2 the second value
     * @param epsileon the error allowance
     * @return true if arg1 is equal to arg2 within the given error allowance
     */
    public static boolean equal(double arg1, double arg2, double epsileon) {
        return (Math.abs(arg1 - arg2) < epsileon);
    }

    /**
     * Returns true if |arg1 - arg2| &lt; 1E-4, in other words, returns true
     * if arg1 is equal to arg2 within the allowed 1E-4 margin of error.
     * Should be used instead of the regular == comparison, due to possibility
     * of floating point round offs.
     *
     * @param arg1 the first value
     * @param arg2 the second value
     * @return true if arg1 is equal to arg2 within the given error allowance
     */
    public static boolean equal(double arg1, double arg2) {
        return equal(arg1, arg2, 1E-4);
    }

    /**
     * Returns true if |arg| &lt; epsileon, in other words, returns
     * if |arg1| is equal to zero within the allowed epsileon margin of error.
     * Should be used instead of the regular == comparison, due to possibility
     * of floating point round offs.
     * 
     * @param arg
     * @param epsileon
     * @return 
     */
    public static boolean equalZero(double arg, double epsileon) {
        return equal(arg, 0, epsileon);
    }

    /**
     * Returns true if |arg| &lt; 1E-4, in other words, returns
     * if |arg1| is equal to zero within the allowed 1E-4 margin of error.
     * Should be used instead of the regular == comparison, due to possibility
     * of floating point round offs.
     * 
     * @param arg
     * @return 
     */
    public static boolean equalZero(double arg) {
        return equalZero(arg, 1E-4);
    }

    /**
     * Returns if any of the given values equals the given target.
     * @param target
     * @param values
     * @return 
     */
    public static boolean orEqual(int target, int... values) {
        for (int i : values) {
            if (i == target) {
                return true;
            }
        }
        return false;
    }
    // </editor-fold>    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Array conversion">
    // <editor-fold defaultstate="collapsed" desc="Wrapper to Wrapper">
    private static final Lazy<UnaryFunction<Integer, Double>> INT_TO_DOUBLE =
            new LazyValue<UnaryFunction<Integer, Double>>() {

                @Override
                public UnaryFunction<Integer, Double> initialize() {
                    return new UnaryFunction<Integer, Double>() {

                        @Override
                        public Double evaluate(Integer arg) {
                            return new Double(arg);
                        }
                    };
                }
            };

    public static Double[] toDoubleArray(Integer[] intArray) {
        return (Double[]) ListFunctions.map(INT_TO_DOUBLE.getValue(),
                Arrays.asList(intArray)).toArray();
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Primative to Primative">    
    /**
     * Given an array containing integers, returns an array of the same
     * size containing the same numbers represented as double numbers.
     * 
     * @param intArray
     * @return 
     */
    public static double[] toDoubleArray(int[] intArray) {
        double[] doubleArray = new double[intArray.length];
        for (int i = 0; i < intArray.length; i++) {
            doubleArray[i] = intArray[i];
        }

        return doubleArray;
    }

    /**
     * Given an array containing float numbers, returns an array of the same
     * size containing the same numbers represented as double numbers.
     * @param floatArray
     * @return 
     */
    public static double[] toDoubleArray(float[] floatArray) {
        double[] doubleArray = new double[floatArray.length];
        for (int i = 0; i < floatArray.length; i++) {
            doubleArray[i] = floatArray[i];
        }

        return doubleArray;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Primative to Wrapper">
    public static Float[] toWrapperArray(float[] primativeArray) {
        Float[] newArray = new Float[primativeArray.length];
        for (int i = 0; i < primativeArray.length; i++) {
            newArray[i] = primativeArray[i];
        }
        return newArray;
    }
    // </editor-fold>
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Parsing">
    /**
     * Given a String, str, returns an Integer represented by the String. If
     * the given String is invalid, returns the given defaultVal.
     * 
     * @param str
     * @param defaultVal
     * @return 
     */
    public static int parseInt(String str, int defaultVal) {
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException e) {
            return defaultVal;
        }
    }
    // </editor-fold>
}
